package dipl.algorithm.math.geometry.op;

import dipl.algorithm.math.primitive.Point2d;
import dipl.algorithm.math.primitive.Side;
import dipl.algorithm.math.primitive.Vector2d;
import dipl.algorithm.math.primitive.op.Point2dOps;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatMath;

/**
 * Operations on 2d-line segments
 */
public class Line2dOps {

	//
	// PUBLIC METHODS
	//

	/**
	 * Tests for collinearity of line segment (p1,p2) and line segment (p3,p4)
	 * @param p1
	 * @param p2
	 * @param p3
	 * @param p4
	 * @return
	 */
	public static boolean LineSegmentsCollinear( Point2d p1, Point2d p2, Point2d p3, Point2d p4 ) {
		return( PointCollinearToLineSegment( p1, p3, p4 ) &&
						PointCollinearToLineSegment( p2, p3, p4 ) );
	}

	/**
	 * Tests whether line segment defined by points p1 and p2 intersects
	 * with line segment defined by points p3 and p4.
	 * @param p1
	 * @param p2
	 * @param p3
	 * @param p4
	 * @return
	 */
	public static boolean LineSegmentsIntersect( Point2d p1, Point2d p2, Point2d p3, Point2d p4 ) {
		Apfloat y12 = p1.y.subtract( p2.y ), x21 = p2.x.subtract( p1.x );
		Apfloat y34 = p3.y.subtract( p4.y ), x43 = p4.x.subtract( p3.x );

		Apfloat det34 = ApfloatMath.multiplySubtract( p3.x, p4.y, p3.y, p4.x );
		Apfloat det12 = ApfloatMath.multiplySubtract( p1.x, p2.y, p1.y, p2.x );

		Apfloat s1 = ApfloatMath.multiplyAdd( p1.x, y34, p1.y, x43 ).add( det34 );
		Apfloat s2 = ApfloatMath.multiplyAdd( p2.x, y34, p2.y, x43 ).add( det34 );
		Apfloat s3 = ApfloatMath.multiplyAdd( p3.x, y12, p3.y, x21 ).add( det12 );
		Apfloat s4 = ApfloatMath.multiplyAdd( p4.x, y12, p4.y, x21 ).add( det12 );

		return (s1.signum()*s2.signum() < 0 && s3.signum()*s4.signum() < 0);
	}

	/**
	 * Test whether point p is collinear with line segment (a,b)
	 * @param p
	 * @param a
	 * @param b
	 * @return
	 */
	public static boolean PointCollinearToLineSegment( Point2d p, Point2d a, Point2d b ) {
		Apfloat t0 = ApfloatMath.multiplyAdd( p.x, a.y.subtract( b.y ), p.y, b.x.subtract( a.x ) );
		Apfloat t1 = ApfloatMath.multiplySubtract( a.x, b.y, a.y, b.x );
		return t0.add( t1 ).compareTo( Apfloat.ZERO ) == 0;
	}

	/**
	 * Tests whether point p lies on line-segment specified by its endpoints a and b
	 * @param p
	 * @param a
	 * @param b
	 * @return
	 */
	public static boolean PointOnLineSegment( Point2d p, Point2d a, Point2d b ) {
		Point2d l1 = Point2dOps.ComparePointsLexicographically( a, b ) <= 0 ? a : b;
		Point2d l2 = l1 == a ? b : a;
		if( l1.x.compareTo( p.x ) <= 0 &&
				p.x.compareTo( l2.x ) <= 0 &&
				l1.y.compareTo( p.y ) <= 0 &&
				p.y.compareTo( l2.y ) <= 0 ) {
			Apfloat t0 = ApfloatMath.multiplyAdd( p.x, l1.y.subtract( l2.y ), p.y, l2.x.subtract( l1.x ) );
			Apfloat t1 = ApfloatMath.multiplySubtract( l1.x, l2.y, l1.y, l2.x );
			return t0.add( t1 ).compareTo( Apfloat.ZERO ) == 0;
		}
		return false;
	}

	/**
	 * Calculates whether point p lies to the right, on or to the left of the directed line segment
	 * from a to b.
	 * If a = b the result will be Side.Onto
	 * @param p
	 * @param a
	 * @param b
	 * @return
	 */
	public static Side WhichSide( Point2d p, Point2d a, Point2d b ) {
		Apfloat t0 = ApfloatMath.multiplyAdd( p.x, a.y.subtract( b.y ), p.y, b.x.subtract( a.x ) );
		Apfloat t1 = ApfloatMath.multiplySubtract( a.x, b.y, a.y, b.x );
		int c = t0.add( t1 ).compareTo( Apfloat.ZERO );
		if( c < 0 )
			return Side.Right;
		if( c > 0 )
			return Side.Left;
		return Side.Onto;
	}

	/**
	 * Calculates whether point p lies to the right, on or to the left of the directed line segment
	 * defined by vector v originating at point (0,0).
	 * @param p
	 * @param v
	 * @return
	 */
	public static Side WhichSide( Point2d p, Vector2d v ) {
		int c = ApfloatMath.multiplyAdd( p.x, v.y, p.y, v.x ).compareTo( Apfloat.ZERO );
		if( c < 0 )
			return Side.Right;
		if( c > 0 )
			return Side.Left;
		return Side.Onto;
	}
}
